//
// Created by Administrator on 2016/9/30.
//

#include "Material.h"

using namespace std;
using namespace glm;
using namespace ZainGL;

GLuint Material::activeShaderLoc = 0;

Material::Material(const GLchar *pathV, const GLchar *pathF,
                           unordered_map<string, ZainTL::FileDatas> *bufferMap) {
    const GLchar *shaderSourceV, *shaderSourceF;
    if (bufferMap == nullptr) {
        shaderSourceV = getFileText(pathV);
        shaderSourceF = getFileText(pathF);
    } else {
        shaderSourceV = loadShaderSourceFromBuffer(pathV, bufferMap);
        shaderSourceF = loadShaderSourceFromBuffer(pathF, bufferMap);
    }
    mShaderLoc = linkShader(compileShader(GL_VERTEX_SHADER, shaderSourceV),
                            compileShader(GL_FRAGMENT_SHADER, shaderSourceF));
}

GLuint Material::linkShader(GLuint vsID, GLuint fsID) {
    GLuint shaderID = glCreateProgram();
    glAttachShader(shaderID, vsID);
    glAttachShader(shaderID, fsID);
    glLinkProgram(shaderID);

    //delete the shaderID objects once we've linked them into the program object
    glDeleteShader(vsID);
    glDeleteShader(fsID);

    //check if linking was successful
    GLint result;
    glGetProgramiv(shaderID, GL_LINK_STATUS, &result);
    if (!result) {
        GLchar info[512];
        glGetProgramInfoLog(shaderID, 512, NULL, info);
        cout << info << endl;
    }

    glUseProgram(shaderID);
    return shaderID;
}

GLuint Material::compileShader(GLenum shaderType, const GLchar *shaderSource) {
    GLuint shaderID = glCreateShader(shaderType);

    //attach the shader source code to the shaderID object and compile the shader
    glShaderSource(shaderID, 1, &shaderSource, NULL);
    glCompileShader(shaderID);

    //check if compilation was successful
    GLint result;
    glGetShaderiv(shaderID, GL_COMPILE_STATUS, &result);
    if (!result) {
        GLchar info[512];
        glGetShaderInfoLog(shaderID, 512, NULL, info);
        cout << info << endl << shaderSource << endl;
    }

    return shaderID;
}

const GLchar *Material::loadShaderSourceFromBuffer(const char *path,
                                                           unordered_map<string, ZainTL::FileDatas> *bufferMap) {
    auto fileDataPairIter = find_if(bufferMap->begin(), bufferMap->end(), [&path](auto fileDataPair) {
        return path == fileDataPair.first;
    });
    if (fileDataPairIter != bufferMap->end()) {
        return fileDataPairIter->second.data;
    }
    return getFileText(path);
}

void Material::update(glm::mat4 viewMat, glm::vec3 viewPos, glm::mat4 projMat, std::vector<Light *> lights, GLfloat screenW, GLfloat screenH) {
    active();
    glUniformMatrix4fv(glGetUniformLocation(mShaderLoc, "projection"), 1, GL_FALSE, value_ptr(projMat));
    glUniformMatrix4fv(glGetUniformLocation(mShaderLoc, "view"), 1, GL_FALSE, value_ptr(viewMat));
    glUniform3f(glGetUniformLocation(mShaderLoc, "viewPos"), viewPos.x, viewPos.y, viewPos.z);

    Light *directionalLight=lights[0];
    glUniformMatrix4fv(glGetUniformLocation(mShaderLoc, "lightProjection"), 1, GL_FALSE, value_ptr(
            directionalLight->getProjection(screenW, screenH)));
    glUniformMatrix4fv(glGetUniformLocation(mShaderLoc, "lightView"), 1, GL_FALSE, value_ptr(directionalLight->getViewMatrix()));
}

void Material::active() {
    if (activeShaderLoc != mShaderLoc) {
        glUseProgram(mShaderLoc);
        activeShaderLoc = mShaderLoc;
    }
}

void Material::updateMaterials(std::vector<Material *> materials, glm::mat4 viewMat, glm::vec3 viewPos, glm::mat4 projMat,
                              std::vector<Light *> lights, GLfloat screenW, GLfloat screenH) {
    for_each(materials.cbegin(),materials.cend(),[&](Material* meterial){
        meterial->active();
        meterial->update(viewMat, viewPos, projMat, lights, screenW, screenH);
    });
}

void Material::setViewMatrix(glm::mat4 &viewMat) {
    glUniformMatrix4fv(glGetUniformLocation(mShaderLoc, "view"), 1, GL_FALSE, value_ptr(viewMat));
}
